package reesoft.certificate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reesoft.common.code.ErrorCode;
import reesoft.common.entity.Result;
import reesoft.common.util.StringUtil;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 数字证书管理类
 *
 * Created by reesoft on 2017-08-11
 */
@Service
public class CertificateManager implements InitializingBean
{
    private static final Logger LOG = LoggerFactory.getLogger(CertificateManager.class);

    private static final Map<String, Integer> privateKeys = new ConcurrentHashMap<>();

    private static final Map<String, Integer> publicKeys = new ConcurrentHashMap<>();

    @Autowired
    private CertificateConfig certificateConfig;

    @Override
    public void afterPropertiesSet() throws Exception
    {
        if (!StringUtil.isEmpty(certificateConfig.getConfigFilePath()))
        {
            preloadKeysFromFile(certificateConfig.getConfigFilePath());
        }
    }

    // 从配置文件加载公/私钥
    public int preloadKeysFromFile(String configFilePath)
    {
        LOG.debug("preloadKeysFromFile, configFilePath:" + configFilePath);

        if (StringUtil.isBlank(configFilePath))
        {
            return -1;
        }

        Properties properties = new Properties();
        try
        {
            properties.load(new FileInputStream(configFilePath));

            return preloadKeys(properties);
        }
        catch (IOException e)
        {
            LOG.error("preloadKeysFromFile exception:", e);
//            e.printStackTrace();
            return -1;
        }
    }

    // 根据配置文件加载公/私钥
    private int preloadKeys(Properties properties)
    {
        String keyDirectory = properties.getProperty("key.directory");

        if (!keyDirectory.endsWith(File.separator))
        {
            keyDirectory += File.separator;
        }

        LOG.debug("Load keys from directory " + keyDirectory);

        final Enumeration<String> propertyNames = (Enumeration<String>) properties.propertyNames();
        int loadKeys = 0;

        while (propertyNames.hasMoreElements())
        {
            String propertyName = propertyNames.nextElement();
            String propertyValue = properties.getProperty(propertyName, "");

            if ((propertyName.indexOf(".private") > 0
                    || propertyName.indexOf(".public") > 0)
                    && loadKey(propertyName, propertyValue, keyDirectory))
            {
                loadKeys++;
            }
        }

        LOG.info(String.valueOf(loadKeys) + " keys loaded");

        return loadKeys;
    }

    // 根据配置信息加载公/私钥
    private boolean loadKey(String propertyName, String propertyValue, String keyDirectory)
    {
        LOG.debug("loadKey, name:" + propertyName);

        if (!propertyValue.isEmpty())
        {
            String id = StringUtil.getSubString(propertyName, "", ".");
            String type = StringUtil.getSubString(propertyName, ".", "");

            if (type.equals("private"))
            {
                String filePath;
                String password = StringUtil.getSubString(propertyValue, ",", "");

                if (password.isEmpty())
                {
                    filePath = keyDirectory + propertyValue;
                }
                else
                {
                    filePath = keyDirectory + StringUtil.getSubString(propertyValue, "", ",");
                }

                return loadPrivateKey(id, filePath, password) > 0;
            }
            else if (type.equals("public"))
            {
                return loadPublicKey(id, keyDirectory + propertyValue) > 0;
            }
        }

        LOG.info("Failed to load key " + propertyName);

        return false;
    }

    // 加载私钥到内存
    public int loadPrivateKey(String id, String privateKeyFilePath, String privateKeyPassword)
    {
        if (id == null || privateKeyFilePath == null || privateKeyPassword == null)
        {
            return -1;
        }

        LOG.debug("loadPrivateKey, id:" + id + ", privateKeyFilePath:" + privateKeyFilePath);

        int keyId = OpenSslWrapper.LoadPrivateKeyFromFile(privateKeyFilePath, privateKeyPassword);

        return addKey(id, keyId, privateKeys);
    }

    // 释放内存中的私钥
    public boolean freePrivateKey(String id)
    {
        LOG.debug("freePrivateKey, id:" + id);

        return removeKey(id, privateKeys);
    }

    // 加载公钥到内存
    public int loadPublicKey(String id, String publicKeyFilePath)
    {
        if (id == null || publicKeyFilePath == null)
        {
            return -1;
        }

        LOG.debug("loadPublicKey, id:" + id + ", publicKeyFilePath:" + publicKeyFilePath);

        int keyId = OpenSslWrapper.LoadPublicKeyFromFile(publicKeyFilePath);

        return addKey(id, keyId, publicKeys);
    }

    // 释放内存中的公钥
    public boolean freePublicKey(String id)
    {
        LOG.debug("freePublicKey, id:" + id);

        return removeKey(id, publicKeys);
    }

    // 释放内存中所有的公/私钥
    public void freeAllKeys()
    {
        LOG.info("freeAllKeys");

        privateKeys.clear();
        publicKeys.clear();
    }

    private int addKey(String id, int keyId, Map<String, Integer> keyMap)
    {
        if (keyId > 0)
        {
            if (keyMap.get(id) != null)
            {
                LOG.warn("Key " + id + " already exists and is replaced");

                removeKey(id, keyMap);
            }

            keyMap.put(id, keyId);
        }

        return keyId;
    }

    private boolean removeKey(String id, Map<String, Integer> keyMap)
    {
        if (id == null)
        {
            return false;
        }

        Integer keyId = keyMap.get(id);

        if (keyId != null && keyId > 0)
        {
            OpenSslWrapper.FreeKey(keyId);

            keyMap.remove(id);

            return true;
        }
        else
        {
            return false;
        }
    }

    private int getPrivateKeyId(String id)
    {
        return getKeyId(privateKeys, id);
    }

    private int getPublicKeyId(String id)
    {
        return getKeyId(publicKeys, id);
    }

    private int getKeyId(Map<String, Integer> keyMap, String id)
    {
        Integer integer = keyMap.get(id);

        if (integer != null)
        {
            return integer;
        }
        else
        {
            return -1;
        }
    }

    // 给数据签名
    public Result<String> signData(String id, String data)
    {
        if (id == null || data == null)
        {
            return new Result<>(ErrorCode.INVALID_PARAMETER);
        }

        LOG.debug("signData, id:" + id);

        int keyId = getPrivateKeyId(id);

        if (keyId > 0)
        {
            String signature;

            if (certificateConfig.isHashBeforeSign())
            {
                signature = OpenSslWrapper.HashAndSignData(keyId, data);
            }
            else
            {
                signature = OpenSslWrapper.SignData(keyId, data);
            }

            return new Result<>(ErrorCode.SUCCESS, signature);
        }
        else
        {
            return new Result<>(CertificateErrorCode.KEY_NOT_EXISTS);
        }
    }

    // 验证数据的签名
    public Result verifySignData(String id, String data, String signature)
    {
        if (id == null || data == null || signature == null)
        {
            return new Result<>(ErrorCode.INVALID_PARAMETER);
        }

        LOG.debug("verifySignData, id:" + id);

        int keyId = getPublicKeyId(id);

        if (keyId > 0)
        {
            boolean success;

            if (certificateConfig.isHashBeforeSign())
            {
                success = OpenSslWrapper.HashAndVerifySignData(keyId, data, signature) == 0;
            }
            else
            {
                success = OpenSslWrapper.VerifySignData(keyId, data, signature) == 0;
            }

            return new Result(success ? ErrorCode.SUCCESS : ErrorCode.INVALID_DATA_SIGNATURE);
        }
        else
        {
            return new Result(CertificateErrorCode.KEY_NOT_EXISTS);
        }
    }
}
